import os.path

from fun.types import *
from fun.nodes import *

std = Package()

def combine(expected, args, kwargs):
    return dict(zip(expected, args)) | {k.value.name: v for k, v in kwargs.items()}

@std.keyword('if')
def if_(args, kwargs):
    args = combine(('cond', 'first', 'second'), args, kwargs)
    if truevalue(args['cond'].execute()):
        return truevalue(args['first'].execute())
    if len(args) > 2:
        return truevalue(args['second'].execute())

@std.keyword('set')
def set(args, kwargs):
    args = combine(('left', 'right'), args, kwargs)
    right = truevalue(args['right'].execute())
    args['left'].value.set(right)
    return right

@std.keyword('$')
def lambda_(args, kwargs):
    params = []
    for i, arg in enumerate(args):
        if isinstance(arg, Value) and isinstance(arg.value, Identifier):
            params.append(arg.value.name)
        else:
            return Function(params, args[i:])
    return Function(params, [])

@std.keyword('dec')
def dec(args, kwargs):
    args = combine(('left', 'right'), args, kwargs)
    right = truevalue(args['right'].execute())
    Call.current.dec(args['left'].value.name, right)
    return right

@std.keyword('#', False)
def list_(*args):
    return list(args)

@std.keyword('@', False)
def tuple_(*args):
    return args

@std.keyword('|')
def dict_(args, kwargs):
    return {truevalue(k).execute(): truevalue(v.execute()) for k, v in kwargs.items()}

@std.keyword('using', False)
def using(package):
    for k, v in (package.bottom if hasattr(package, 'bottom') else package.__dict__).items():
        Call.current.dec(k, v)

@std.keyword('return', False)
def return_(*args):
    match len(args):
        case 0:
            raise FunctionReturn(None)
        case 1:
            raise FunctionReturn(args[0])
        case _:
            raise FunctionReturn(args)

class LoopContinue(Exception): ...

class LoopBreak(Exception):
    def __init__(self, ret):
        self.ret = ret

@std.keyword('for', False)
def for_(iterable, func):
    ret = None
    for item in iterable:
        try:
            ret = func(item)
        except LoopBreak as ex:
            return ex.ret
        except LoopContinue:
            continue
    return ret

@std.keyword('while', False)
def while_(cond, func):
    ret = None
    while cond():
        try:
            ret = func()
        except LoopBreak as ex:
            return ex.ret
        except LoopContinue:
            continue
    return ret

@std.keyword('loop', False)
def loop(func):
    ret = None
    while True:
        try:
            ret = func()
        except LoopBreak as ex:
            return ex.ret
        except LoopContinue:
            continue
    return ret

@std.keyword('break', False)
def break_(ret=None):
    raise LoopBreak(ret)

@std.keyword('continue', False)
def continue_():
    raise LoopContinue()

@std.keyword('import', False)
def import_(path, *args):
    if not os.path.exists(path):
        path = os.path.realpath(os.path.join(os.path.dirname(Call.current.path), path))
    path = os.path.realpath(path)
    copy = std.copy(path)
    with open(path, encoding='utf-8') as f:
        copy.import_(f.read(), *args)
    return copy